﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

namespace library
{
    partial class p2pFile
    {

        internal static string MIME_TYPE = "XAfyY6(kuaM-4Hf6gmOyq2FEj7ApL(qJYHw9FrWFJgc=";

        internal static string FILE_TYPE_DOWNLOAD = "mEniHQkMD3CfpumfpiZRSHj5KobLo3gobNwWOJEjTkM=";

        internal static string FILE_TYPE_DIRECTORY = "IBmUfN(zwx4RyK4E446sWu46a3gafR1pq(RqDVBEtUU=";

        internal static string FILE_TYPE_IMAGE = "uT-fzw9qpMWIhR4cynegdEiPt6IuDxmWXwGjFbMhyUw=";

        internal static string FILE_TYPE_AUDIO = "fzmmg6cJfAsmO(U7aeJ(3YH6lgap3gUUk-F(wwpS99U=";

        internal static string FILE_TYPE_VIDEO = "syOawLi-4MeGohZbhzpeCuxxAH0TMe3PU55MhIcfIlM=";

        static Cache<PostTuple> postTuples = new Cache<PostTuple>(Parameters.postTupleTimeout);

        static bool loadEvents = true;

        internal static byte[] Upload(string path, byte[] address = null)
        {
            return Upload(new string[] { path }, address);
        }

        internal static byte[] Upload(string[] paths, byte[] address = null)
        {
            lock (postTuples)
            {
                if (loadEvents)
                {
                    LoadStaticInfo();
                 
                    loadEvents = false;
                }
            }

            address = address ?? Utils.GetAddress();

            ThreadPool.QueueUserWorkItem(ThreadUpload, new FileUploadItem { Paths = paths, Address = address });

            return address;
        }

        static void ThreadUpload(object o)
        {
            var file = (FileUploadItem)o;

            var packet_address = file.Address;

            if (file.Paths.Length == 1)
            {
                if (Directory.Exists(file.Paths[0]))
                    DirectoryUpload(packet_address, file.Paths[0]);
                else
                    Upload(packet_address, PacketTypes.Content, file.Paths[0]);
            }
            else
            {
                var result = new List<byte>();

                var root = Path.GetDirectoryName(file.Paths[0]);

                foreach (var path in file.Paths)
                {
                    var rootedFileName = path.Substring(root.Length + 1);

                    var packect_address = Upload(path, Utils.GetAddress());

                    var filename = Encoding.Unicode.GetBytes(rootedFileName);

                    var item = BitConverter.GetBytes(filename.Length).
                        Concat(filename).
                        Concat(packect_address);

                    result.AddRange(item);
                }

                Upload(packet_address, PacketTypes.Directory, stream: new MemoryStream(result.ToArray()));
            }

        }

        static void DirectoryUpload(byte[] address, string path)
        {
            var files = Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly)
                                .Concat(Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)).ToArray();

            var result = new List<byte>();

            var post = p2pPost.Create(FILE_TYPE_DIRECTORY, null, address);

            foreach (var file in files)
            {
                var rootedFileName = file.Substring(path.Length + 1);

                var packect_address = Upload(file, Utils.GetAddress());

                p2pPost.Create(Utils.ToBase64String(address), null, packect_address);

                var filename = Encoding.Unicode.GetBytes(rootedFileName);

                var item = BitConverter.GetBytes(filename.Length).
                    Concat(filename).
                    Concat(packect_address);

                result.AddRange(item);
            }

            Upload(address, PacketTypes.Directory, stream: new MemoryStream(result.ToArray()));

            GeneratePosts(path, address);

            //if (OnFileUpload != null)
            //    OnFileUpload(t.Path, Utils.ToBase64String(address));
        }

        static void Upload(byte[] address, PacketTypes packetType, string path = null, Stream stream = null)
        {
            List<byte[]> addresses = new List<byte[]>();

            try
            {
                if (path != null)
                    stream = new FileStream(path, FileMode.Open, FileAccess.Read);

                while (addresses.Count() != 1)
                {
                    byte[] data = new byte[Parameters.packetSize + Parameters.packetHeaderSize];

                    int offset = 0;

                    while (offset * Parameters.packetSize < stream.Length)
                    {
                        int bytesRead = stream.Read(data, Parameters.packetHeaderSize, Parameters.packetSize);

                        data[0] = (byte)packetType;

                        BitConverter.GetBytes(offset).CopyTo(data, 1);

                        offset++;

                        Utils.ComputeHash(data, Parameters.packetHeaderSize, bytesRead).CopyTo(data, 5); //5 = 1 byte (data type) + 4 byte (offset)

                        byte[] packet_address;

                        if (!addresses.Any() && offset * Parameters.packetSize >= stream.Length)
                            packet_address = address;
                        else
                            packet_address = Utils.GetAddress();

                        addresses.Add(packet_address);

                        LocalPackets.Add(packet_address, data.Take(bytesRead + Parameters.packetHeaderSize).ToArray(), Client.LocalPeer);
                    }

                    if (addresses.Count() == 1)
                    {
                        if (path != null)
                            GeneratePosts(path, address);
                    }
                    else if (addresses.Count() > 1)
                    {
                        packetType = PacketTypes.Addresses;

                        List<byte> index = new List<byte>();

                        foreach (byte[] addr in addresses)
                        {
                            if (index.Count() + Parameters.addressSize > Parameters.packetSize && Parameters.packetSize - index.Count() % Parameters.packetSize < Parameters.addressSize)
                                index.AddRange(new byte[Parameters.packetSize - index.Count() % Parameters.packetSize]);

                            index.AddRange(addr);
                        }

                        addresses.Clear();

                        stream = new MemoryStream(index.ToArray());
                    }
                }

            }
            finally
            {
                if (stream != null)
                    stream.Close();
            }
        }

        static void LoadStaticInfo()
        {
            postTuples.OnCacheExpired += OnCacheExpired;

            Client.OnSearchReturn += OnSearchReturn;
        }

        static void DelayedCreatePostTuple(byte[] address, string value, string property, PostTuple.AlbumExtras extras = null)
        {
            lock (postTuples)
            {
                var p = new PostTuple()
                {
                    Address = address,
                    Property = property,
                    Value = value,
                    Extras = extras

                };

                postTuples.Add(p);

                if (postTuples.Count(x => x.CachedValue.Value.Equals(value) && x.CachedValue.Property.Equals(p.Property)) == 1)
                    Client.Search(value);
            }
        }

        static void CreatePostTuple(byte[] address, string value, string property)
        {
            var post = p2pPost.Create(value, null, address);

            p2pPost.Create(Utils.ToAddressSizeBase64String(property), null, post.Address);
        }

        static void GeneratePosts(string path, byte[] address)
        {
           

            CreatePostTuple(address, Path.GetExtension(path), "Extension");

            CreatePostTuple(address, Path.GetFileNameWithoutExtension(path), "Name");

            TagLib.File f = null;

            try
            {
                f = TagLib.File.Create(path);
            }
            catch (TagLib.UnsupportedFormatException) { }

            if (f == null)
            {
                p2pPost.Create(FILE_TYPE_DOWNLOAD, null, address);
                return;
            }

            var mediaType = string.Empty;

            if (f.Properties.MediaTypes == TagLib.MediaTypes.Photo)
                mediaType = FILE_TYPE_IMAGE;

            else if (f.GetType() == typeof(TagLib.Mpeg4.File))
                mediaType = FILE_TYPE_VIDEO;

            if(!string.IsNullOrEmpty(mediaType))
                p2pPost.Create(mediaType, null, address);
            else
                p2pPost.Create(FILE_TYPE_DOWNLOAD, null, address);

            if(!string.IsNullOrWhiteSpace(f.Tag.Title))
                CreatePostTuple(address, f.Tag.Title, "Title");


            if (f.Tag.Performers.Any())
                foreach (var item in f.Tag.Performers)
                    DelayedCreatePostTuple(address, item, "Artist");

            else if (f.Tag.AlbumArtists.Any())
                foreach (var item in f.Tag.AlbumArtists)
                    DelayedCreatePostTuple(address, item, "Artist");


            foreach (var item in f.Tag.Composers)
                DelayedCreatePostTuple(address, item, "Artist");


            if (!string.IsNullOrWhiteSpace(f.Tag.Conductor))
                DelayedCreatePostTuple(address, f.Tag.Conductor, "Artist");


            if (!string.IsNullOrWhiteSpace(f.Tag.Comment))
                CreatePostTuple(address, f.Tag.Comment, "Comment");


            foreach (var item in f.Tag.Genres)
                DelayedCreatePostTuple(address, item, "Genre");

            
            if (f.Tag.Track > 0)
                CreatePostTuple(address, f.Tag.Track.ToString(), "Track");

            
            if (!string.IsNullOrWhiteSpace(f.Tag.Lyrics))
                CreatePostTuple(address, f.Tag.Lyrics, "Lyrics");


            if (!string.IsNullOrWhiteSpace(f.Tag.Grouping))
                CreatePostTuple(address, f.Tag.Grouping, "Grouping");

            
            if (f.Tag.BeatsPerMinute > 0)
                CreatePostTuple(address, f.Tag.BeatsPerMinute.ToString(), "BeatsPerMinute");


            if (f.Tag.Disc > 0)
                CreatePostTuple(address, f.Tag.Disc.ToString(), "Disc");


            if (f.Properties.Duration.TotalMilliseconds > 0)
                CreatePostTuple(address, f.Properties.Duration.ToString(@"hh\:mm\:ss"), "Duration");

            if (!string.IsNullOrWhiteSpace(f.Properties.Description))
                CreatePostTuple(address, f.Properties.Description, "Description");

            if (f.Properties.AudioBitrate > 0)
                CreatePostTuple(address, f.Properties.AudioBitrate.ToString(), "AudioBitrate");

            if (f.Properties.AudioSampleRate > 0)
                CreatePostTuple(address, f.Properties.AudioSampleRate.ToString(), "AudioSampleRate");


            if (f.Properties.BitsPerSample > 0)
                CreatePostTuple(address, f.Properties.BitsPerSample.ToString(), "BitsPerSample");


            if (f.Properties.AudioChannels > 0)
                CreatePostTuple(address, f.Properties.AudioChannels.ToString(), "AudioChannels");


            if (f.Properties.PhotoQuality > 0)
                CreatePostTuple(address, f.Properties.PhotoQuality.ToString(), "Quality");


            if(f.Properties.VideoHeight > 0)
                CreatePostTuple(address, f.Properties.VideoHeight.ToString(), "Height");
            else if (f.Properties.PhotoHeight > 0)
                CreatePostTuple(address, f.Properties.PhotoHeight.ToString(), "Height");

            if (f.Properties.VideoWidth > 0)
                CreatePostTuple(address, f.Properties.VideoWidth.ToString(), "Width");
            else if (f.Properties.PhotoWidth > 0)
                CreatePostTuple(address, f.Properties.PhotoWidth.ToString(), "Width");


            if (f.GetType().BaseType == typeof(TagLib.Image.ImageBlockFile))
            {
                GenerateThumb(Image.FromFile(path), null, address);

                return;
            }

            if (string.IsNullOrWhiteSpace(f.Tag.Album))
            {
                if (f.Tag.Year > 0)
                    CreatePostTuple(address, f.Tag.Year.ToString(), "Year");

                if (f.Tag.Pictures.Any())
                    foreach (var pic in f.Tag.Pictures)
                        CreatePicturePost(address, new MemoryStream(pic.Data.Data));
                else
                    CreatePicturePost(address, Win32ImageFactory.ExtractThumbnail(path));
            }
            else
            {
                var extras = new PostTuple.AlbumExtras();

                if (f.Tag.Year > 0)
                    extras.Year = f.Tag.Year;

                if (f.Tag.AlbumArtists.Any())
                    foreach (var item in f.Tag.AlbumArtists)
                        extras.Artists = f.Tag.AlbumArtists;

                else if (f.Tag.Performers.Any())
                    foreach (var item in f.Tag.Performers)
                        extras.Artists = f.Tag.Performers;

                if (f.Tag.Pictures.Any())
                    extras.Pictures = f.Tag.Pictures.Select(x => x.Data.Data);

                else
                {
                    var pic = Win32ImageFactory.ExtractThumbnail(path);

                    var buffer = new byte[pic.Length];

                    pic.Read(buffer, 0, buffer.Length);

                    extras.Pictures = new byte[][] { buffer };
                }

                DelayedCreatePostTuple(
                    address,
                    f.Tag.Album,
                    "Album",
                    extras);                
            }

            //GeneratePostTuple(address, "MIME Type", f.MimeType);
        }

        private static void CreatePicturePost(byte[] address, Stream pic)
        {
            var buffer = new byte[pic.Length];

            pic.Read(buffer, 0, buffer.Length);

            var pictureAddress = Utils.GetAddress();

            pic.Seek(0, 0);

            GenerateThumb(Image.FromStream(pic), null, pictureAddress);

            pic.Seek(0, 0);

            Upload(pictureAddress, PacketTypes.Content, null, pic);

            p2pPost.Create(FILE_TYPE_IMAGE, null, pictureAddress);

            p2pPost.Create(FILE_TYPE_DOWNLOAD, null, pictureAddress);

            p2pPost.Create(Utils.ToBase64String(pictureAddress), null, address);

            

            
        }

        static void OnSearchReturn(string search, ValueHits<p2pPost> post)
        {
            foreach (var p in post)
            {
                lock (postTuples)
                    if (postTuples.Any(x => x.CachedValue.Value.Equals(p.HitValue.Content)))
                        Client.Search(p.HitValue.Base64Address);
            }
        }

        static void OnCacheExpired(CacheItem<PostTuple> item)
        {
            var postsValue = LocalPosts.SearchByValue(item.CachedValue.Value);

            p2pPost post;

            foreach (var postValue in postsValue)
            {
                if (!item.CachedValue.Value.Equals(postValue.HitValue.Content))
                    continue;

                var posts = LocalPosts.Search(postValue.HitValue.Address);

                if (posts.Any(x => x.HitValue.Content != null && x.HitValue.Content.Equals(item.CachedValue.Property)))
                {
                    post = p2pPost.Create(postValue.HitValue.Base64Address, null, item.CachedValue.Address);

                    return;
                }
            }

            post = p2pPost.Create(item.CachedValue.Value, null, item.CachedValue.Address);

            p2pPost.Create(item.CachedValue.Property, null, post.Address);

            if (item.CachedValue.Extras != null)
            {
                foreach(var t in item.CachedValue.Extras.Artists)
                    DelayedCreatePostTuple(post.Address, t, "Artist");

                if (item.CachedValue.Extras.Disc > 0)
                    CreatePostTuple(post.Address, item.CachedValue.Extras.Disc.ToString(), "Disc");

                if (item.CachedValue.Extras.Year > 0)
                    CreatePostTuple(post.Address, item.CachedValue.Extras.Year.ToString(), "Year");

                foreach (var t in item.CachedValue.Extras.Pictures)
                    CreatePicturePost(post.Address, new MemoryStream(t));
            }
        }

        static void GenerateThumb(Image image, byte[] address, byte[] targetAddress)
        {
            var result = (Image)new Bitmap(image, new Size(280, 170));

            var stream = result.ToStream(ImageFormat.Jpeg);

            var data = new byte[stream.Length];

            stream.Read(data, 0, data.Length);

            var post = p2pPost.Create(data, address, targetAddress);

            p2pPost.Create(FILE_TYPE_IMAGE, null, post.Address);
        }


        class PostTuple
        {
            internal byte[] Address;

            string property;

            internal string Property
            {
                get { return property; }
                set
                {
                    property = Utils.ToAddressSizeBase64String(value);
                }
            }

            internal string Value;

            internal AlbumExtras Extras;

            internal class AlbumExtras
            {
                internal string[] Artists;

                internal uint Disc;

                internal uint Year;

                internal IEnumerable<byte[]> Pictures;
            }
        }

        class FileUploadItem
        {
            internal byte[] Address;

            internal string[] Paths;
        }

    }
}
